#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <sys/mman.h>

#include <linux/fb.h>

#define FILE_FB_DEF     "/dev/fb0"
static int fb_fd = -1;
static struct fb_fix_screeninfo fb_f_info;
static struct fb_var_screeninfo fb_v_info;
static unsigned int *framebuffer_all = NULL;
static int framebuffer_all_len = 0;

static char *fb_visual[] = {
    "Monochr. 1=Black 0=White",
    "Monochr. 1=White 0=Black",
    "True color",
    "Pseudo color (like atari)",
    "Direct color",
    "Pseudo color readonly",
    "Visual identified by a V4L2 FOURCC"
};

#define V_C_X(x)    (x+fb_v_info.xoffset) /*x at virtual coord*/
#define V_C_Y(y)    (y+fb_v_info.yoffset) /*y at virtual coord*/
#define MEM_LOCATION_32(x, y)      (framebuffer_all + V_C_Y(y)*fb_v_info.xres_virtual + V_C_X(x))

#define RED_FULL    0xFF0000
#define GREEE_FULL  0x00FF00
#define BLUE_FULL   0x0000FF

static int clear_screen(void)
{
    memset(framebuffer_all, 0, framebuffer_all_len);
}

static int draw_margin_line(void)
{
        int x, y;
        int width = fb_v_info.xres; 
        int height = fb_v_info.yres;
        printf("margin line draw: width=%d, height=%d\n", width, height);
        for (x = 0; x < width; x++) { /* y=0 */
            y = 0;
            *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
        }

        for (y = 0; y < height; y++) { /* x=width */
            x = width - 1;
            *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
        }

        for (x = 0; x < width; x++) { /* y=height */
            y = height -1;
            *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
        }

        for (y = 0; y < height; y++) { /* x=0 */
            x = 0;
            *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
        }

    return 0;
}

static int draw_circle(int a, int b, int r)
{
    /* circle: center(a,b), radius(r), (x-a)^2 + (y-b)^2 = r^2 */
    int x, y;

    printf("circle draw: center=(%d,%d), r=%d\n", a, b, r);
    for (x = a-r; x <= a+r; x++) {
        y = sqrtf(r*r-((x-a)*(x-a))) + b;
        if (x>=0 && x<fb_v_info.xres_virtual && y>=0 && y<fb_v_info.yres_virtual)
            *(MEM_LOCATION_32(x,y)) = RED_FULL;

        y = b - sqrtf(r*r-((x-a)*(x-a)));
        if (x>=0 && x<fb_v_info.xres_virtual && y>=0 && y<fb_v_info.yres_virtual)
            *(MEM_LOCATION_32(x,y)) = RED_FULL;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    int ret = 0;
    char *file_fb = NULL;

    if (argc > 1)
        file_fb = argv[1];
    else
        file_fb = FILE_FB_DEF;
    fb_fd = open(file_fb, O_RDWR);
    if (fb_fd < 0) {
        printf("open %s failed(%d:%s)\n", file_fb, errno, strerror(errno));
        return -errno;
    }

    ret = ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_f_info);
    if (ret < 0) {
        printf("ioctl FBIOGET_FSCREENINFO %s failed(%d:%s)\n", file_fb, errno, strerror(errno));
        ret = -errno;
        goto out;
    }

    printf("\nfb_fix_screeninfo:\n");
    printf("\tid\t\t\t%s\n", fb_f_info.id);
    printf("\tsmem_start(phy)\t\t0x%lx\n", fb_f_info.smem_start);
    printf("\tsmem_len\t\t%u\n", fb_f_info.smem_len);
    printf("\ttype\t\t\t0x%x\n", fb_f_info.type);
    printf("\ttype_aux\t\t0x%x\n", fb_f_info.type_aux);
    printf("\tvisual\t\t\t0x%x(%s)\n", fb_f_info.visual, fb_visual[fb_f_info.visual]);
    printf("\txpanstep\t\t%u\n", fb_f_info.xpanstep);
    printf("\typanstep\t\t%u\n", fb_f_info.ypanstep);
    printf("\tywrapstep\t\t%u\n", fb_f_info.ywrapstep);
    printf("\tline_length\t\t%u(B)\n", fb_f_info.line_length);
    printf("\tmmio_start(phy)\t\t0x%lx\n", fb_f_info.mmio_start);
    printf("\tmmio_len\t\t%u\n", fb_f_info.mmio_len);
    printf("\taccel\t\t\t%u\n", fb_f_info.accel);
    printf("\tcapabilities\t\t0x%x\n", fb_f_info.capabilities);
    printf("\n");

    ret = ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_v_info);
    if (ret < 0) {
        printf("ioctl FBIOGET_VSCREENINFO %s failed(%d:%s)\n", file_fb, errno, strerror(errno));
        ret = -errno;
        goto out;
    }

	printf("\nfb_var_screeninfo:\n");
	printf("\txres\t\t\t%u\n", fb_v_info.xres);
	printf("\tyres\t\t\t%u\n", fb_v_info.yres);
	printf("\txres_virtual\t\t%u\n", fb_v_info.xres_virtual);
	printf("\tyres_virtual\t\t%u\n", fb_v_info.yres_virtual);
	printf("\txoffset\t\t\t%u\n", fb_v_info.xoffset);
	printf("\tyoffset\t\t\t%u\n", fb_v_info.yoffset);
	printf("\tbits_per_pixel\t\t%u\n", fb_v_info.bits_per_pixel);
	printf("\tgrayscale\t\t%u\n", fb_v_info.grayscale);
	printf("\tred_bitfield:\n");
	printf("\t\toffset\t\t%u\n", fb_v_info.red.offset);
	printf("\t\tlength\t\t%u\n", fb_v_info.red.length);
	printf("\t\tmsb_right\t%u\n", fb_v_info.red.msb_right);
	printf("\tgreen_bitfield:\n");
	printf("\t\toffset\t\t%u\n", fb_v_info.green.offset);
	printf("\t\tlength\t\t%u\n", fb_v_info.green.length);
	printf("\t\tmsb_right\t%u\n", fb_v_info.green.msb_right);
	printf("\tblue_bitfield:\n");
	printf("\t\toffset\t\t%u\n", fb_v_info.blue.offset);
	printf("\t\tlength\t\t%u\n", fb_v_info.blue.length);
	printf("\t\tmsb_right\t%u\n", fb_v_info.blue.msb_right);
	printf("\ttransp_bitfield:\n");
	printf("\t\toffset\t\t%u\n", fb_v_info.transp.offset);
	printf("\t\tlength\t\t%u\n", fb_v_info.transp.length);
	printf("\t\tmsb_right\t%u\n", fb_v_info.transp.msb_right);
	printf("\tnonstd\t\t\t%u\n", fb_v_info.nonstd);
	printf("\tactivate\t\t0x%x\n", fb_v_info.activate);
	printf("\theight\t\t\t0x%x(mm)\n", fb_v_info.height);
	printf("\twidth\t\t\t0x%x(mm)\n", fb_v_info.width);
	printf("\tpixclock\t\t%u(ps)\n", fb_v_info.pixclock);
	printf("\tleft_margin\t\t%u\n", fb_v_info.left_margin);
	printf("\tright_margin\t\t%u\n", fb_v_info.right_margin);
	printf("\tupper_margin\t\t%u\n", fb_v_info.upper_margin);
	printf("\tlower_margin\t\t%u\n", fb_v_info.lower_margin);
	printf("\thsync_len\t\t%u\n", fb_v_info.hsync_len);
	printf("\tvsync_len\t\t%u\n", fb_v_info.vsync_len);
	printf("\tsync\t\t\t0x%x\n", fb_v_info.sync);
	printf("\tvmode\t\t\t0x%x\n", fb_v_info.vmode);
	printf("\trotate\t\t\t%u\n", fb_v_info.rotate);
	printf("\tcolorspace\t\t%u\n", fb_v_info.colorspace);
    printf("\n");

    framebuffer_all_len = fb_f_info.smem_len;
    framebuffer_all = (unsigned int *)mmap(0, framebuffer_all_len, PROT_READ|PROT_WRITE, MAP_SHARED, fb_fd, 0);
    if (!framebuffer_all || framebuffer_all == MAP_FAILED) {
        printf("mmap framebuffer_all %s failed(%d:%s)\n", file_fb, errno, strerror(errno));
        ret = -errno;
        goto out;
    }

    //fb_v_info.xres = fb_v_info.xres_virtual;
    //fb_v_info.yres = fb_v_info.yres_virtual;

    int r = 200;
    int a = 200;
    int b = 200;
    int d_x = 5, d_y = 5;
    while (1) {
        clear_screen();
        draw_margin_line();
        draw_circle(a, b, r);
        /*
           fb_v_info.activate = FB_ACTIVATE_NOW;
           ret = ioctl(fb_fd, FBIOPUT_VSCREENINFO, &fb_v_info);
           if (ret < 0) {
           printf("ioctl FBIOPUT_VSCREENINFO %s failed(%d:%s)", file_fb, errno, strerror(errno));
           ret = -errno;
           goto out;
           }
         */

        if ((a-r)<0 || (a+r)>=fb_v_info.xres) d_x *= -1;
        if ((b-r)<0 || (b+r)>=fb_v_info.yres) d_y *= -1;

        a += d_x;
        b += d_y;

        usleep(100*1000);
    }

out:
    if (framebuffer_all == NULL) {
        munmap(framebuffer_all, framebuffer_all_len);
        framebuffer_all = NULL;
    }
    if (fb_fd >= 0) {
        close(fb_fd);
        fb_fd = -1;
    }

    return ret;
}
